// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/parsing/expression-scope-reparenter.h"

#include "src/ast/ast-traversal-visitor.h"
#include "src/ast/ast.h"
#include "src/ast/scopes.h"
#include "src/objects-inl.h"

namespace v8 {
namespace internal {

    namespace {

        class Reparenter final : public AstTraversalVisitor<Reparenter> {
        public:
            Reparenter(uintptr_t stack_limit, Expression* initializer, Scope* scope)
                : AstTraversalVisitor(stack_limit, initializer)
                , scope_(scope)
            {
            }

        private:
            // This is required so that the overriden Visit* methods can be
            // called by the base class (template).
            friend class AstTraversalVisitor<Reparenter>;

            void VisitFunctionLiteral(FunctionLiteral* expr);
            void VisitClassLiteral(ClassLiteral* expr);
            void VisitVariableProxy(VariableProxy* expr);

            void VisitBlock(Block* stmt);
            void VisitTryCatchStatement(TryCatchStatement* stmt);
            void VisitWithStatement(WithStatement* stmt);

            Scope* scope_;
        };

        void Reparenter::VisitFunctionLiteral(FunctionLiteral* function_literal)
        {
            function_literal->scope()->ReplaceOuterScope(scope_);
        }

        void Reparenter::VisitClassLiteral(ClassLiteral* class_literal)
        {
            class_literal->scope()->ReplaceOuterScope(scope_);
            // No need to visit the constructor since it will have the class
            // scope on its scope chain.
            DCHECK_EQ(class_literal->constructor()->scope()->outer_scope(),
                class_literal->scope());

            if (class_literal->static_fields_initializer() != nullptr) {
                DCHECK_EQ(
                    class_literal->static_fields_initializer()->scope()->outer_scope(),
                    class_literal->scope());
            }
#if DEBUG
            // The same goes for the rest of the class, but we do some
            // sanity checking in debug mode.
            for (ClassLiteralProperty* prop : *class_literal->properties()) {
                // No need to visit the values, since all values are functions with
                // the class scope on their scope chain.
                DCHECK(prop->value()->IsFunctionLiteral());
                DCHECK_EQ(prop->value()->AsFunctionLiteral()->scope()->outer_scope(),
                    class_literal->scope());
            }
#endif
        }

        void Reparenter::VisitVariableProxy(VariableProxy* proxy)
        {
            if (!proxy->is_resolved()) {
                if (scope_->outer_scope()->RemoveUnresolved(proxy)) {
                    scope_->AddUnresolved(proxy);
                }
            } else {
                // Ensure that temporaries we find are already in the correct scope.
                DCHECK(proxy->var()->mode() != VariableMode::kTemporary || proxy->var()->scope() == scope_->GetClosureScope());
            }
        }

        void Reparenter::VisitBlock(Block* stmt)
        {
            if (stmt->scope())
                stmt->scope()->ReplaceOuterScope(scope_);
            else
                VisitStatements(stmt->statements());
        }

        void Reparenter::VisitTryCatchStatement(TryCatchStatement* stmt)
        {
            Visit(stmt->try_block());
            if (stmt->scope()) {
                stmt->scope()->ReplaceOuterScope(scope_);
            } else {
                Visit(stmt->catch_block());
            }
        }

        void Reparenter::VisitWithStatement(WithStatement* stmt)
        {
            Visit(stmt->expression());
            stmt->scope()->ReplaceOuterScope(scope_);
        }

    } // anonymous namespace

    void ReparentExpressionScope(uintptr_t stack_limit, Expression* expr,
        Scope* scope)
    {
        // The only case that uses this code is block scopes for parameters containing
        // sloppy eval.
        DCHECK(scope->is_block_scope());
        DCHECK(scope->is_declaration_scope());
        DCHECK(scope->AsDeclarationScope()->calls_sloppy_eval());
        DCHECK(scope->outer_scope()->is_function_scope());

        Reparenter r(stack_limit, expr, scope);
        r.Run();
    }

} // namespace internal
} // namespace v8
